# -*- coding: utf-8 -*-
import atexit
import os
import subprocess
import struct

from Code.Constantes import *

import Code.VarGen as VarGen
from Code.Movimientos import posA1, a1Pos, pv2xpv, xpv2pv
import Code.Util as Util
import Code.Partida as Partida
import Code.SQL.Base as Base
import Code.ControlPosicion as ControlPosicion

#=========================================================================================
class RegSTAT :
    dicNP = { 0:"", 1:"q", 2:"r",3:"b",4:"n" }
    dicPN = { "":0, "q":1, "r":2,"b":3,"n":4 }
    sizeSTAT = 26
    stSTAT = "<HIIIIII"
    def __init__( self, file, offset ) :
        self.file = file
        self.offset = offset

    def read( self ) :
        self.file.seek( self.offset, 0 )

        xmove, self.brother, self.child, self.white, self.draw, self.black, self.other = \
                struct.unpack( self.stSTAT, self.file.read( self.sizeSTAT ) )

        self.move = self.num2move(xmove)

    def __str__(self) :
        return "%s b%d c%d w%d d%d b%d o%d"%(self.move, self.brother, self.child, self.white, self.draw, self.black, self.other)

    def write( self, siMas=False ) :
        if siMas :
            self.file.seek( 0, 2 )
        else :
            self.file.seek( self.offset, 0 )
        self.file.write( struct.pack( self.stSTAT, self.move2num(self.move), self.brother, \
                                        self.child, self.white, self.draw, self.black, self.other ) )

    def num2move( self, num ) :
        d = num%64
        num = num/64
        h = num%64
        c = num/64
        return posA1(d)+posA1(h)+self.dicNP.get(c,"")

    def move2num( self, move ) :
        d = a1Pos(move[:2])
        h = a1Pos(move[2:4])
        c = self.dicPN.get(move[4:],0)

        return d + 64*( h + 64*c )

    def new( self, move ) :
        self.move, self.brother, self.child, self.white, self.draw, self.black, self.other = move, 0, 0, 0, 0, 0, 0

    def sum( self, white, draw, black, other ) :
        self.white += white
        self.draw += draw
        self.black += black
        self.other += other

    def clone( self ) :
        work = RegSTAT( self.file, self.offset )
        work.move, work.brother, work.child, work.white, work.draw, work.black, work.other = \
            self.move, self.brother, self.child, self.white, self.draw, self.black, self.other
        return work

    def nextByte( self ) :
        self.file.seek( 0, 2)
        return self.file.tell()

    def walkchild( self, move, siCrear ) :
        if self.child :
            hijo = self.clone()
            hijo.offset = self.child
            while True :
                hijo.read()
                if hijo.move == move :
                    return hijo
                if hijo.brother :
                    hijo.offset = hijo.brother
                else :
                    if siCrear :
                        nextByte = self.nextByte()
                        hijo.brother = nextByte
                        hijo.write()
                        # Cambiamos al hermano
                        hijo.offset = nextByte
                        hijo.new(move)
                        hijo.write(True)
                        return hijo
                    else :
                        return None
        elif siCrear :
            nextByte = self.nextByte()
            self.child = nextByte
            self.write()
            hijo = self.clone()
            hijo.offset = nextByte
            hijo.new(move)
            hijo.write(True)
            return hijo
        else :
            return None

    def children( self ) :
        liChildren = []
        if self.child :
            work = self.clone()
            work.offset = work.child
            work.read()
            liChildren.append(work.clone())

            while work.brother :
                work.offset = work.brother
                work.read()
                liChildren.append(work.clone())
        return liChildren

    def maxBloq( self ) :
        resp = ""
        if self.white >= self.draw and self.white >= self.black :
            resp += "w"
        if self.black >= self.draw and self.black >= self.white :
            resp += "b"
        if self.draw >= self.white and self.draw >= self.black :
            resp += "d"
        return resp

    def games( self ) :
        return self.white + self.draw + self.black + self.other

#=========================================================================================
class TreeSTAT :
    regSize = 26
    defaultDepth = 30

    def __init__( self, nomFichero, depth = None ) :
        self.nomFichero = nomFichero

        if Util.tamFichero(nomFichero) < self.regSize :
            self.file = self.creaFichero(depth)
        else :
            self.file = self.abreFichero()
        self.depth = self.readDepth()
        self.root = RegSTAT( self.file, 1 )
        self.root.read( )

        atexit.register( self.close )

    def readDepth( self ) :
        self.file.seek( 0, 0 )
        depth = ord(self.file.read(1))
        return depth

    def writeDepth( self, depth ) :
        self.depth = depth
        self.file.seek( 0, 0 )
        self.file.write(chr(depth))

    def close( self ) :
        if self.file :
            self.file.close()
            self.file = None

    def reset( self, depth = None ) :
        self.close()
        Util.borraFichero(self.nomFichero)
        self.file = self.creaFichero(depth)
        self.root = RegSTAT( self.file, 1 )
        self.root.read( )

    def creaFichero( self, depth ) :
        f = open( self.nomFichero, "w+b" )
        if depth is None :
            depth = self.defaultDepth
        self.depth = depth
        f.seek( 0, 0 )
        f.write(chr(depth))
        wr = RegSTAT( f, 1 )
        wr.new( "a1a1" )
        wr.write(True)
        return f

    def abreFichero( self ) :
        f = open( self.nomFichero, "r+b" )
        return f

    def append( self, pv, result, r = +1 ) :
        w = d = b = o = 0
        if result == "1-0" :
            w = r
        elif result == "0-1" :
            b = r
        elif result == "1/2-1/2" :
            d = r
        else :
            o = r

        self.root.sum( w, d, b, o )
        self.root.write()

        padre = self.root
        for n, move in enumerate(pv.split(" ")) :
            if n >= self.depth :
                break

            hijo = padre.walkchild( move, True )
            hijo.sum( w, d, b, o )
            hijo.write()
            padre = hijo

    def flistAllpvs( self, maxDepth, minGames, mxW, mxD, mxB ) :

        li = []
        if mxW :
            li.append("w")
        if mxD :
            li.append("d")
        if mxB :
            li.append("b")

        fich = VarGen.configuracion.ficheroTemporal( "tmp" )
        f = open( fich, "wb" )

        def mira( work, pvPrevio, depth, cp ) :
            if depth > maxDepth :
                return
            if work.games()>= minGames :
                maxBloq = work.maxBloq()
                resp = False
                for x in li :
                    if x in maxBloq :
                        resp = True
                        break
                if not resp :
                    return

                pv = (pvPrevio + " " + work.move).strip()
                if pv == "a1a1" :
                    cp = ControlPosicion.ControlPosicion()
                    cp.posInicial()
                    pv = ""
                else :
                    wm = work.move
                    cp.moverPV( wm )
                    f.write( "%s|%s|%s\n"%(pv2xpv(pv), wm, cp.fen() ) )
                liChildren = work.children()
                for uno in liChildren :
                    mira( uno, pv, depth+1, cp.copia() )

        cp = ControlPosicion.ControlPosicion()
        cp.posInicial()
        mira( self.root, "", 0, cp )
        f.close()
        return fich

#=========================================================================================
class DBgames :
    #----------------------------------------------------------------------------------------------
    def __init__( self, nomFichero, segundosBuffer = 0.8 ) :
        self.nomFichero = nomFichero

        self.segundosBuffer = segundosBuffer

        self.db = Base.DBBase(nomFichero)
        self.tabla = "games"
        if not self.db.existeTabla(self.tabla) :
            self.creaTabla()
        self.dbf = self.db.dbfCache( self.tabla, "XPV,EVENT,SITE,DATE,WHITE,BLACK,RESULT,PLIES,ECO,WHITEELO,BLACKELO,PGN" )
        atexit.register( self.close )

        self.dbSTAT = TreeSTAT(self.nomFichero+"-stat")

    #----------------------------------------------------------------------------------------------
    def depthStat( self ) :
        return self.dbSTAT.depth

    #----------------------------------------------------------------------------------------------
    def flistAllpvs( self, maxDepth, minGames, mxW, mxD, mxB ) :
        return self.dbSTAT.flistAllpvs( maxDepth, minGames, mxW, mxD, mxB )

    #----------------------------------------------------------------------------------------------
    def listOthers( self ) :
        li = []
        nameActive = os.path.basename(self.nomFichero).lower()
        for f in os.listdir(VarGen.configuracion.carpeta) :
            low = f.lower()
            if low.endswith( ".lcg" ) :
                if low != nameActive :
                    li.append( f[:-4] )
        return li

    #----------------------------------------------------------------------------------------------
    def close( self ) :
        if self.dbf :
            self.dbf.cerrar()
            self.dbf = None
        if self.db :
            self.db.cerrar()
            self.db = None
        if self.dbSTAT is not None:
            self.dbSTAT.close()
            self.dbSTAT = None

    #----------------------------------------------------------------------------------------------
    def reccount( self ) :
        return self.dbf.reccount()

    #----------------------------------------------------------------------------------------------
    def reccountTotal( self ) :
        return self.dbSTAT.root.games()

    #----------------------------------------------------------------------------------------------
    def creaTabla( self ) :
        tb = Base.TablaBase( self.tabla )
        tb.nuevoCampo( "XPV", "VARCHAR", notNull=True, primaryKey = True )
        for campo in "EVENT,SITE,DATE,WHITE,BLACK,RESULT,PLIES,ECO,WHITEELO,BLACKELO".split(",") :
            tb.nuevoCampo( campo, "VARCHAR" )
        tb.nuevoCampo( "PGN", "BLOB" )
        self.db.generarTabla(tb)

    #----------------------------------------------------------------------------------------------
    def goto( self, num ) :
        self.dbf.goto(num)

    #----------------------------------------------------------------------------------------------
    def field( self, nfila, name ) :
        self.dbf.goto(nfila)
        return getattr( self.dbf.reg, name )

    #----------------------------------------------------------------------------------------------
    def damePV( self, fila ) :
        xpv = self.field( fila, "XPV" )
        return xpv2pv( xpv )

    #----------------------------------------------------------------------------------------------
    def filterPV( self, pv, condicionAdicional=None ) :
        xpv = pv2xpv(pv)
        condicion = 'XPV LIKE "%s%%"'%xpv if xpv else ""
        if condicionAdicional :
            if condicion :
                condicion += "AND (%s)"%condicionAdicional
            else :
                condicion = condicionAdicional
        self.dbf.ponCondicion( condicion )
        self.dbf.leerBuffer(segundos = self.segundosBuffer)
        self.dbf.gotop()

    #----------------------------------------------------------------------------------------------
    def borrarLista( self, lista ) :
        for recno in lista :
            self.dbf.goto(recno)
            pv = xpv2pv(self.dbf.reg.XPV)
            result = self.dbf.reg.RESULT
            self.dbSTAT.append( pv, result, -1 )
        self.dbf.borrarLista( lista )
        self.dbf.leerBuffer(segundos = self.segundosBuffer)

    #----------------------------------------------------------------------------------------------
    def cambiarUno( self, recno, nuevoPGN, pvNue, dicS_PGN ) :
        self.dbf.goto(recno)
        pvAnt = xpv2pv(self.dbf.reg.XPV)
        resAnt = self.dbf.reg.RESULT
        resNue = dicS_PGN.get( "Result", "*" )
        if pvAnt != pvNue or resAnt != resNue :
            self.dbSTAT.append( pvAnt, resAnt, -1 )
            self.dbSTAT.append( pvNue, resNue, +1 )

            reg.XPV = pv2xpv(pvNue)
            reg.RESULT = resNue

        br = self.dbf.baseRegistro()
        br.XPV = pv2xpv( pvNue )
        br.EVENT = dicS_PGN.get("Event", "")
        br.SITE = dicS_PGN.get("Site", "")
        br.DATE = dicS_PGN.get("Date", "")
        br.WHITE = dicS_PGN.get("White", "")
        br.BLACK = dicS_PGN.get("Black", "")
        br.RESULT = resNue
        br.PLIES = "%3d"%(pvNue.strip().count(" ")+1,)
        br.ECO = dicS_PGN.get("ECO", "")
        br.WHITEELO = dicS_PGN.get("WhiteElo", "")
        br.BLACKELO = dicS_PGN.get("BlackElo", "")
        br.PGN = Util.var2blob(nuevoPGN)

        self.dbf.modificarReg( recno, br )

    #----------------------------------------------------------------------------------------------
    def leerMasRegistros( self ) :
        return self.dbf.leerMasBuffer( segundos = self.segundosBuffer )

    #----------------------------------------------------------------------------------------------
    def siFaltanRegistrosPorLeer( self ) :
        return self.dbf.siBufferPendiente

    #----------------------------------------------------------------------------------------------
    def pvStats( self, pv ) :
        work = self.dbSTAT.root
        if pv :
            for move in pv.split(" ") :
                work = work.walkchild( move, False )
                if not work :
                    break
        return work

    #----------------------------------------------------------------------------------------------
    def getSummary( self, pvBase, dicAnalisis ) :
        root = self.dbSTAT.root
        liMoves = []

        if pvBase is None :
            w, d, b, o = root.white, root.draw, root.black, root.other
            tt = w + d + b + o
            dic = {}
            dic["pvmove"] = ""
            dic["pv"] = ""
            dic["analisis"] = None
            dic["games"] = tt
            dic["white"] = w
            dic["draw"] = d
            dic["black"] = b
            dic["other"] = o
            dic["pwhite"] = w*100.0/tt if tt else 0.0
            dic["pdraw"] = d*100.0/tt if tt else 0.0
            dic["pblack"] = b*100.0/tt if tt else 0.0
            dic["pother"] = o*100.0/tt if tt else 0.0
            dic["move"] = _( "Total" )
            liMoves.append(dic)

        else :
            work = root
            if pvBase :
                li = pvBase.split(" ")
                for move in li :
                    work = work.walkchild( move, False )
                    if not work :
                        return []

            liBrothers = work.children()
            tt = 0

            lipvmove = []
            for st in liBrothers :
                w, d, b, o = st.white, st.draw, st.black, st.other
                t = w + d + b + o
                if t == 0 :
                    continue

                dic = {}
                pvmove = st.move
                pv = pvBase + " " + pvmove
                pv = pv.strip()
                lipvmove.append(pvmove)
                dic["pvmove"] = pvmove
                dic["pv"] = pv
                dic["analisis"] = dicAnalisis.get(pvmove,None)
                dic["games"] = t
                tt += t
                dic["white"] = w
                dic["draw"] = d
                dic["black"] = b
                dic["other"] = o
                dic["pwhite"] = w*100.0/t
                dic["pdraw"] = d*100.0/t
                dic["pblack"] = b*100.0/t
                dic["pother"] = o*100.0/t

                liMoves.append(dic)

            for pvmove in dicAnalisis :
                if pvmove not in lipvmove :
                    dic = {}
                    pv = pvBase + " " + pvmove
                    pv = pv.strip()
                    dic["pvmove"] = pvmove
                    dic["pv"] = pv
                    dic["analisis"] = dicAnalisis[pvmove]
                    dic["games"] = 0
                    dic["white"] = 0
                    dic["draw"] = 0
                    dic["black"] = 0
                    dic["other"] = 0
                    dic["pwhite"] = 0.00
                    dic["pdraw"] = 0.00
                    dic["pblack"] = 0.00
                    dic["pother"] = 0.00

                    liMoves.append(dic)

            liMoves = sorted(liMoves, key=lambda dic: -dic["games"])

        for dic in liMoves :
            dic["pgames"] = dic["games"]*100.0/tt if tt else 0.0
            dic["pdrawwhite"] = dic["pwhite"] + dic["pdraw"]
            dic["pdrawblack"] = dic["pblack"] + dic["pdraw"]

            pvmove = dic["pvmove"]
            if pvmove :
                pv = dic["pv"]
                p = Partida.Partida()
                p.leerPV( pv )
                if p.numJugadas() :
                    jg = p.liJugadas[-1]
                    dic["move"] = jg.etiquetaSP().replace(" ", "")
                else :
                    dic["move"] = pvmove
                dic["partida"] = p

        return liMoves

    #----------------------------------------------------------------------------------------------
    def recrearSTAT( self, dispatch, depth ) :
        self.dbSTAT.reset(depth)
        recno = 0
        self.filterPV("")
        reccount = self.dbf.reccount()
        dispatch(0,reccount)
        while True :
            self.dbf.goto(recno)
            pv = xpv2pv(self.dbf.reg.XPV)
            result = self.dbf.reg.RESULT
            self.dbSTAT.append( pv, result )

            if recno%100 == 0 :
                resp = dispatch(recno,reccount)
                if not resp :
                    return

            recno += 1
            if recno >= reccount :
                if self.dbf.siBufferPendiente :
                    self.dbf.leerMasBuffer(chunk=3000)
                    reccount = self.dbf.reccount()
                    if recno >= reccount :
                        break
                else :
                    break

    #----------------------------------------------------------------------------------------------
    def append( self, pv,event,site,date,white,black,result,eco,whiteelo,blackelo,pgn,okCommit ) :
        br = self.dbf.baseRegistro()
        br.XPV = pv2xpv( pv )
        br.EVENT = event
        br.SITE = site
        br.DATE = date
        br.WHITE = white
        br.BLACK = black
        br.RESULT = result
        br.PLIES = "%3d"%(pv.strip().count(" ")+1,)
        br.ECO = eco
        br.WHITEELO = whiteelo
        br.BLACKELO = blackelo
        br.PGN = Util.var2blob(pgn)
        siRepetido = False
        try :
            self.dbf.insertar( br, okCommit=okCommit, okCursorClose=okCommit )
        except :
            siRepetido = True
            self.duplicates += 1
        if not siRepetido :
            self.dbSTAT.append( pv,result )

    #----------------------------------------------------------------------------------------------
    def __getitem__( self, num ) :
        self.dbf.goto(num)
        reg = self.dbf.reg
        return reg

    #----------------------------------------------------------------------------------------------
    def leePGNrecno( self, recno ) :
        v = self.dbf.leeOtroCampo( recno, "PGN" )
        pgn = Util.blob2var(v) if v else None
        return pgn

    #----------------------------------------------------------------------------------------------
    def leerPGN( self, fichero, removePrevious, dispatch ) :

        # 1.File pgn -> temporal clean
        dispatch( "CLEANING" )
        pgntemp = VarGen.configuracion.ficheroTemporal( "pgnpv" )
        logtemp = VarGen.configuracion.ficheroTemporal( "log" )
        li = [ os.path.abspath('Engines/Pgn-extract/pgn-extract16-7-changed.exe'),
                fichero,
                "-o%s"%pgntemp,
                "--nomovenumbers",
                "--noresults",
                "--notags",
                "-s",
                "-Wslalg",
                "-C",
                "-N",
                "-V",
                "-w2000",
                "-bl1",
                "-l%s"%logtemp ]
        process = subprocess.Popen( li, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
        txt = process.stdout.read()
        li = txt.strip().split("\r")
        matched = int(li[-2].split(":")[1].strip())
        processed = int(li[-1].split(":")[1].strip())

        if matched == 0 :
            Util.borraFichero( pgntemp )
            Util.borraFichero( logtemp )
            return processed, 0, 0

        # 2. -> DB
        dispatch( "WORK", matched )

        self.posicTMP = 0

        def work( dic, pv, pgn ) :
            self.posicTMP += 1
            siSeguir = dispatch( "NEXT", self.posicTMP )
            if not siSeguir :
                return False

            # duplicates -> error in append
            event = dic.get("EVENT", "")
            site = dic.get("SITE", "")
            date = dic.get("DATE", "")
            white = dic.get("WHITE", "")
            black = dic.get("BLACK", "")
            result = dic.get("RESULT", "")
            eco = dic.get("ECO", "")
            whiteelo = dic.get("WHITEELO", "")
            blackelo = dic.get("BLACKELO", "")
            self.append( pv,event,site,date,white,black,result,eco,whiteelo,blackelo,pgn, False ) #self.posicTMP%ncommit == 0 )
            return True

        self.duplicates = 0
        fpgn = open( pgntemp, "rb")
        pgn = ""
        pv = ""
        dic = {}
        for linea in fpgn :
            ls = linea.strip()
            if ls.startswith( "[" ) and ls.endswith( "]" ) :
                li = ls[1:-1].replace( '""', '"').split( '"' )
                if len(li) == 3 :
                    clave = li[0].strip().upper()
                    valor = li[1].strip().replace( "?", "" ).strip( "." ).strip()
                    if clave :
                        dic[clave] = valor
                pgn += linea
            elif ls.startswith( ">>>" ):
                pv = ls[3:].replace("+","")
                if not work( dic, pv, pgn ) :
                    pv = "" # para que no haga un nuevo work
                    break
                pv = ""
                pgn = ""
                dic = {}
            else :
                if ls or pgn :
                    pgn += linea
        if pv :
            work( dic, pv, pgn )

        dispatch( "END_0" )
        self.dbf.commit()

        fpgn.close()

        del self.posicTMP

        Util.borraFichero( pgntemp )
        Util.borraFichero( logtemp )

        dispatch( "END_1" )

        return processed, matched, self.duplicates

#=========================================================================================
